# -*- coding: UTF-8 -*-
"""
@auth:buxiangjie
@date:2023/1/31 10:59
@describe: 处理参数第二步
"""
import copy

from util.request_package import Req
from datetime import datetime
from util.processed_case import ProcessedCase
from jsonpath import jsonpath
from util.fuzzy_rules import INT32, INT64, NORMAL_STRING, DATETIME_STRING, OTHERS, NO_FIELD, Required, NoRequired
from util.fuzz_assert import *
from util.common import WindowPrint


# noinspection PyTypedDict
class RunCase:

	def __init__(self):
		self.case_list = []
		self.final_case = []
		self.summary = {
			"total": 0,
			"pass": 0,
			"fail": 0,
			"start_exec_time": None,
			"end_exec_time": None,
			"elapsed_time": None
		}
		self.overview = []

	def __call__(self, *args, **kwargs):
		self.kwg = kwargs
		WindowPrint().print("清洗API文件......")
		self.p = ProcessedCase(kwargs["file"])
		kwargs["window"].refresh()
		self.env = kwargs["env"]
		WindowPrint().print("处理请求参数和请求参数明细......")
		self.p.params_operate()
		kwargs["window"].refresh()
		WindowPrint().print("初始化测试用例......")
		self.init_case()
		kwargs["window"].refresh()
		WindowPrint().print("执行测试用例......")
		self.run_case()
		kwargs["window"].refresh()
		WindowPrint().print("测试结果后置处理......")
		self.teardown_case()
		kwargs["window"].refresh()
		return self

	def init_case(self) -> None:
		"""
		初始化测试用例
		:return: None
		"""
		for k, v in self.p.PARAMS.items():
			self.kwg["window"].refresh()
			cases = {
				"param": v,
				"path": k,
				"summary": self.p.PARAMS_DETAIL[k]["summary"],
				"headers": {"Content-Type": self.p.PARAMS_DETAIL[k]["consumes"][0]},
				"case_details": []
			}
			required = jsonpath(self.p.PARAMS_DETAIL[k], "$...required")
			for kk, vv in v.items():
				# noinspection PyTypedDict
				def _case(ck, cv, p_node=""):
					case = {
						"cur_key": ck,
						"cur_value": cv,
						"type": jsonpath(self.p.PARAMS_DETAIL[k], f"$...{ck}")[-1].get("type"),
						"format": jsonpath(self.p.PARAMS_DETAIL[k], f"$...{ck}.format")[-1] if jsonpath(
							self.p.PARAMS_DETAIL[k],
							f"$...{ck}.format") else "",
						"description": jsonpath(self.p.PARAMS_DETAIL[k], f"$...{ck}.description")[-1],
						"parent_node": p_node
					}

					if kk in self.flat(required):
						case["required"] = "True"
					else:
						case["required"] = ""
					cases["case_details"].append(case)

				_case(kk, vv)
				# 递归
				if isinstance(vv, dict):
					for kkk, vvv in vv.items():
						_case(kkk, vvv, kk)
				elif isinstance(vv, list):
					for iv in vv:
						if isinstance(iv, dict):
							for kkk, vvv in iv.items():
								_case(kkk, vvv, kk)

			self.case_list.append(cases)

	def flat(self, nums: list) -> list:
		"""
		list递归
		:param nums: list
		:return: list
		"""
		# 递归列表
		res = []
		for i in nums:
			if isinstance(i, list):
				res.extend(self.flat(i))
			else:
				res.append(i)
		return res

	def run_case(self) -> None:
		"""
		构造测试用例并执行
		:return: None
		"""
		# 第一层遍历获取基础参数
		case_id = 1
		# 创建开始执行时间
		self.summary["start_exec_time"] = datetime.now()
		for c in self.case_list:
			_case = {
				"path": c["path"],
				"summary": c["summary"],
				"headers": c["headers"],
				"param": copy.deepcopy(c["param"])
			}
			# 执行非参数模糊测试
			for nf in NO_FIELD:
				WindowPrint().print(f"执行测试用例:ID={case_id}......")
				self.kwg["window"].refresh()
				_case["case_id"] = case_id
				# 获取模糊规则响应的参数
				res = nf()(**_case)
				# 获取接口响应的参数
				rep = Req.req(env=self.env, **res)
				res["res_headers"] = str(rep.headers)
				res["req_url"] = rep.url
				res["req_response"] = str(rep.text)
				res["code"] = rep.status_code
				# 获取断言结果
				assert_res = globals()[f"{res['fuzz_name']}Assert"]()(**res)
				if assert_res:
					res["assert_result"] = ""
					res["assert_detail"] = str(assert_res)
				else:
					res["assert_result"] = "True"
					res["assert_detail"] = "断言成功"
				self.final_case.append(res)
				case_id += 1

			# 第二层遍历获取一个字段的内容
			for cc in c["case_details"]:
				case = {
					"path": c["path"],
					"summary": c["summary"],
					"headers": c["headers"],
					"param": copy.deepcopy(c["param"])
				}
				case.update(cc)
				# 判断字段类型后得到需要跑的模糊规则列表
				if cc["type"] == "string":
					# 判断字段格式
					if cc.get("format", "") == "date-time":
						fuzz_list = copy.deepcopy(DATETIME_STRING)
					else:
						fuzz_list = copy.deepcopy(NORMAL_STRING)
				elif cc["type"] == "int":
					if cc.get("format", "") == "int32":
						fuzz_list = copy.deepcopy(INT32)
					elif cc.get("format", "") == "int64":
						fuzz_list = copy.deepcopy(INT64)
					else:
						raise ValueError(f"不支持的类型:{cc.get('format', '')}")
				else:
					fuzz_list = copy.deepcopy(NORMAL_STRING)

				# 判断是否必填
				if cc["required"]:
					fuzz_list.remove(NoRequired)
				else:
					fuzz_list.remove(Required)
				# 遍历需要执行的模糊规则
				for f in fuzz_list:
					WindowPrint().print(f"执行测试用例:ID={case_id}......")
					self.kwg["window"].refresh()
					case["case_id"] = case_id
					# 获取模糊规则响应的参数
					res = f()(**case)
					# 获取接口响应的参数
					rep = Req.req(env=self.env, **res)
					res["res_headers"] = str(rep.headers)
					res["req_url"] = rep.url
					res["req_response"] = str(rep.text)
					res["code"] = rep.status_code
					# 获取断言结果
					assert_res = globals()[f"{res['fuzz_name']}Assert"]()(**res)
					if assert_res:
						res["assert_result"] = ""
						res["assert_detail"] = str(assert_res)
					else:
						res["assert_result"] = "True"
						res["assert_detail"] = "断言成功"
					self.final_case.append(res)
					case_id += 1

	# noinspection PyTypedDict
	def teardown_case(self) -> None:
		"""
		用例执行完成后处理
		:return: None
		"""
		start_time = self.summary["start_exec_time"]
		end_time = datetime.now()
		self.summary["elapsed_time"] = (end_time - start_time).seconds
		self.summary["start_exec_time"] = str(start_time).split(".")[0]
		self.summary["end_exec_time"] = str(end_time).split(".")[0]
		for c in self.final_case:
			self.summary["total"] += 1
			if c["assert_result"]:
				self.summary["pass"] += 1
			else:
				self.summary["fail"] += 1

			case_overview = {
				"case_id": c["case_id"],
				"status": "PASS" if c["assert_result"] else "FAIL",
				"desc": f"{c['path']} -> {c['summary']} -> {c['fuzz_desc']}"
			}
			self.overview.append(case_overview)
